<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"><meta name="description" content="Learn_Git_Branching"><meta name="keywords" content="技术学习"><meta name="author" content="LiYang"><meta name="copyright" content="LiYang"><title>Learn_Git_Branching | 一条鲤鱼</title><link rel="shortcut icon" href="/melody-favicon.ico"><link rel="stylesheet" href="/css/index.css?version=1.9.0"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome@latest/css/font-awesome.min.css?version=1.9.0"><meta name="format-detection" content="telephone=no"><meta http-equiv="x-dns-prefetch-control" content="on"><link rel="dns-prefetch" href="https://cdn.jsdelivr.net"><meta http-equiv="Cache-Control" content="no-transform"><meta http-equiv="Cache-Control" content="no-siteapp"><script>var GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: undefined,
  copy: {
    success: 'Copy successfully',
    error: 'Copy error',
    noSupport: 'The browser does not support'
  },
  hexoVersion: '5.4.0'
} </script><meta name="generator" content="Hexo 5.4.0"><link rel="alternate" href="/atom.xml" title="一条鲤鱼" type="application/atom+xml">
</head><body><i class="fa fa-arrow-right" id="toggle-sidebar" aria-hidden="true"></i><div id="sidebar" data-display="true"><div class="toggle-sidebar-info text-center"><span data-toggle="Toggle article">Toggle site</span><hr></div><div class="sidebar-toc"><div class="sidebar-toc__title">Catalog</div><div class="sidebar-toc__progress"><span class="progress-notice">You've read</span><span class="progress-num">0</span><span class="progress-percentage">%</span><div class="sidebar-toc__progress-bar"></div></div><div class="sidebar-toc__content"><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#Learn-Git-Branching%EF%BC%88%E4%B8%BB%E8%A6%81%E9%83%A8%E5%88%86%EF%BC%89"><span class="toc-number">1.</span> <span class="toc-text">Learn Git Branching（主要部分）</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%9F%BA%E7%A1%80%E7%AF%87"><span class="toc-number">1.1.</span> <span class="toc-text">基础篇</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#1-Git-Commit"><span class="toc-number">1.1.1.</span> <span class="toc-text">1 Git Commit</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-Git-Branch-Git-Switch"><span class="toc-number">1.1.2.</span> <span class="toc-text">2 Git Branch&#x2F;Git Switch</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-Git-merge%EF%BC%88%E5%90%88%E5%B9%B6%E5%88%86%E6%94%AF%EF%BC%89"><span class="toc-number">1.1.3.</span> <span class="toc-text">3 Git merge（合并分支）</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-Git-Rebase%EF%BC%88%E9%87%8D%E5%AE%9A%E4%BD%8D%EF%BC%89"><span class="toc-number">1.1.4.</span> <span class="toc-text">4 Git Rebase（重定位）</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%AB%98%E7%BA%A7%E7%AF%87"><span class="toc-number">1.2.</span> <span class="toc-text">高级篇</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#1-%E5%88%86%E7%A6%BBHEAD"><span class="toc-number">1.2.1.</span> <span class="toc-text">1 分离HEAD</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%88%86%E7%A6%BB%E7%9A%84HEAD"><span class="toc-number">1.2.1.1.</span> <span class="toc-text">分离的HEAD</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-%E7%9B%B8%E5%AF%B9%E5%BC%95%E7%94%A8"><span class="toc-number">1.2.2.</span> <span class="toc-text">2 相对引用</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%93%8D%E4%BD%9C%E7%AC%A6"><span class="toc-number">1.2.2.1.</span> <span class="toc-text">^操作符</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E2%80%9C-%E2%80%9D%E6%93%8D%E4%BD%9C%E7%AC%A6"><span class="toc-number">1.2.2.2.</span> <span class="toc-text">“~”操作符</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%BC%BA%E5%88%B6%E4%BF%AE%E6%94%B9%E5%88%86%E6%94%AF%E4%BD%8D%E7%BD%AE"><span class="toc-number">1.2.2.3.</span> <span class="toc-text">强制修改分支位置</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-%E6%92%A4%E9%94%80%E5%8F%98%E6%9B%B4"><span class="toc-number">1.2.3.</span> <span class="toc-text">3 撤销变更</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#Git-Reset%EF%BC%88%E6%9C%AC%E5%9C%B0%E6%92%A4%E9%94%80%EF%BC%89"><span class="toc-number">1.2.3.1.</span> <span class="toc-text">Git Reset（本地撤销）</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#Git-Revert%EF%BC%88%E8%BF%9C%E7%A8%8B%E6%92%A4%E9%94%80%EF%BC%89"><span class="toc-number">1.2.3.2.</span> <span class="toc-text">Git Revert（远程撤销）</span></a></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%A7%BB%E5%8A%A8%E6%8F%90%E4%BA%A4%E8%AE%B0%E5%BD%95"><span class="toc-number">1.3.</span> <span class="toc-text">移动提交记录</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#1-Git-Cherry-pick%EF%BC%88%E6%8A%93%E5%8F%96%E4%BB%BB%E6%84%8F%E6%8F%90%E4%BA%A4%EF%BC%89"><span class="toc-number">1.3.1.</span> <span class="toc-text">1 Git Cherry-pick（抓取任意提交）</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-%E4%BA%A4%E4%BA%92%E5%BC%8F%E7%9A%84-rebase"><span class="toc-number">1.3.2.</span> <span class="toc-text">2 交互式的 rebase</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%9D%82%E9%A1%B9%EF%BC%88%E6%8A%80%E5%B7%A7%E4%B8%8E%E8%B4%B4%E5%A3%AB%EF%BC%89"><span class="toc-number">1.4.</span> <span class="toc-text">杂项（技巧与贴士）</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#1-%E6%9C%AC%E5%9C%B0%E6%A0%88%E5%BC%8F%E6%8F%90%E4%BA%A4"><span class="toc-number">1.4.1.</span> <span class="toc-text">1 本地栈式提交</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-%E6%8F%90%E4%BA%A4%E7%9A%84%E6%8A%80%E5%B7%A7"><span class="toc-number">1.4.2.</span> <span class="toc-text">2 提交的技巧</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-%E6%8F%90%E4%BA%A4%E7%9A%84%E6%8A%80%E5%B7%A72"><span class="toc-number">1.4.3.</span> <span class="toc-text">3 提交的技巧2</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-Git-Tags%EF%BC%88%E6%89%93%E6%A0%87%E7%AD%BE%EF%BC%89"><span class="toc-number">1.4.4.</span> <span class="toc-text">4 Git Tags（打标签）</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-Git-Describe%EF%BC%88%E6%89%BE%E6%9C%80%E8%BF%91%E7%89%88%E6%9C%AC%EF%BC%89"><span class="toc-number">1.4.5.</span> <span class="toc-text">5 Git Describe（找最近版本）</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%AB%98%E7%BA%A7%E8%AF%9D%E9%A2%98"><span class="toc-number">1.5.</span> <span class="toc-text">高级话题</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#1-%E5%A4%9A%E6%AC%A1Rebase"><span class="toc-number">1.5.1.</span> <span class="toc-text">1 多次Rebase</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-%E4%B8%A4%E4%B8%AA%E7%88%B6%E8%8A%82%E7%82%B9"><span class="toc-number">1.5.2.</span> <span class="toc-text">2 两个父节点</span></a></li></ol></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#Learn-Git-Branching%EF%BC%88%E8%BF%9C%E7%A8%8B%E9%83%A8%E5%88%86%EF%BC%89"><span class="toc-number">2.</span> <span class="toc-text">Learn Git Branching（远程部分）</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#Push-amp-Pull"><span class="toc-number">2.1.</span> <span class="toc-text">Push&amp;Pull</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#1-Git-clone"><span class="toc-number">2.1.1.</span> <span class="toc-text">1 Git clone</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-%E8%BF%9C%E7%A8%8B%E5%88%86%E6%94%AF"><span class="toc-number">2.1.2.</span> <span class="toc-text">2 远程分支</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-Git-Fetch"><span class="toc-number">2.1.3.</span> <span class="toc-text">3 Git Fetch</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-Git-Pull"><span class="toc-number">2.1.4.</span> <span class="toc-text">4 Git Pull</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-%E5%9B%A2%E9%98%9F%E5%90%88%E4%BD%9C"><span class="toc-number">2.1.5.</span> <span class="toc-text">5 团队合作</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#6-Git-Push"><span class="toc-number">2.1.6.</span> <span class="toc-text">6 Git Push</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#7-%E5%81%8F%E7%A6%BB%E7%9A%84%E5%B7%A5%E4%BD%9C"><span class="toc-number">2.1.7.</span> <span class="toc-text">7 偏离的工作</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#8-%E9%94%81%E5%AE%9A%E7%9A%84Main%EF%BC%88Locked-Main%EF%BC%89"><span class="toc-number">2.1.8.</span> <span class="toc-text">8 锁定的Main（Locked Main）</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Git-%E8%BF%9C%E7%A8%8B%E4%BB%93%E5%BA%93%E9%AB%98%E7%BA%A7%E6%93%8D%E4%BD%9C"><span class="toc-number">2.2.</span> <span class="toc-text">Git 远程仓库高级操作</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#1%E3%80%81%E6%8E%A8%E9%80%81%E4%B8%BB%E5%88%86%E6%94%AF"><span class="toc-number">2.2.1.</span> <span class="toc-text">1、推送主分支</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2%E3%80%81%E5%90%88%E5%B9%B6%E8%BF%9C%E7%A8%8B%E4%BB%93%E5%BA%93"><span class="toc-number">2.2.2.</span> <span class="toc-text">2、合并远程仓库</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3%E3%80%81%E8%BF%9C%E7%A8%8B%E8%B7%9F%E8%B8%AA%E5%88%86%E6%94%AF"><span class="toc-number">2.2.3.</span> <span class="toc-text">3、远程跟踪分支</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4%E3%80%81Git-Push%E7%9A%84%E5%8F%82%E6%95%B0"><span class="toc-number">2.2.4.</span> <span class="toc-text">4、Git Push的参数</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5%E3%80%81-lt-place-gt-%E5%8F%82%E6%95%B0%E8%AF%A6%E8%A7%A3"><span class="toc-number">2.2.5.</span> <span class="toc-text">5、&lt;place&gt;参数详解</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#6%E3%80%81Git-fetch%E7%9A%84%E5%8F%82%E6%95%B0"><span class="toc-number">2.2.6.</span> <span class="toc-text">6、Git fetch的参数</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#7%E3%80%81%E6%B2%A1%E6%9C%89source%E7%9A%84source"><span class="toc-number">2.2.7.</span> <span class="toc-text">7、没有source的source</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#8%E3%80%81git-pull%E7%9A%84%E5%8F%82%E6%95%B0"><span class="toc-number">2.2.8.</span> <span class="toc-text">8、git pull的参数</span></a></li></ol></li></ol></li></ol></div></div><div class="author-info hide"><div class="author-info__avatar text-center"><img src="/img/avatar.png"></div><div class="author-info__name text-center">LiYang</div><div class="author-info__description text-center"></div><hr><div class="author-info-articles"><a class="author-info-articles__archives article-meta" href="/archives"><span class="pull-left">Articles</span><span class="pull-right">13</span></a><a class="author-info-articles__tags article-meta" href="/tags"><span class="pull-left">Tags</span><span class="pull-right">6</span></a><a class="author-info-articles__categories article-meta" href="/categories"><span class="pull-left">Categories</span><span class="pull-right">7</span></a></div></div></div><div id="content-outer"><div class="no-bg" id="top-container"><div id="page-header"><span class="pull-left"> <a id="site-name" href="/">一条鲤鱼</a></span><i class="fa fa-bars toggle-menu pull-right" aria-hidden="true"></i><span class="pull-right menus">   <a class="site-page" href="/">Home</a><a class="site-page" href="/archives">Archives</a><a class="site-page" href="/tags">Tags</a><a class="site-page" href="/categories">Categories</a></span><span class="pull-right"></span></div><div id="post-info"><div id="post-title">Learn_Git_Branching</div><div id="post-meta"><time class="post-meta__date"><i class="fa fa-calendar" aria-hidden="true"></i> 2022-04-18</time><span class="post-meta__separator">|</span><i class="fa fa-inbox post-meta__icon" aria-hidden="true"></i><a class="post-meta__categories" href="/categories/%E5%AE%9E%E7%94%A8%E6%8A%80%E6%9C%AF/">实用技术</a></div></div></div><div class="layout" id="content-inner"><article id="post"><div class="article-container" id="post-content"><h1 id="Learn-Git-Branching（主要部分）"><a href="#Learn-Git-Branching（主要部分）" class="headerlink" title="Learn Git Branching（主要部分）"></a>Learn Git Branching（主要部分）</h1><p> “Learning Git Branching” 可以说是目前为止最好的教程了，在沙盒里你能执行相应的命令，还能看到每个命令的执行情况； 通过一系列刺激的关卡挑战，逐步深入的学习 Git 的强大功能，在这个过程中你可能还会发现一些有意思的事情。</p>
<h2 id="基础篇"><a href="#基础篇" class="headerlink" title="基础篇"></a>基础篇</h2><h3 id="1-Git-Commit"><a href="#1-Git-Commit" class="headerlink" title="1 Git Commit"></a>1 Git Commit</h3><p>Git 仓库中的提交记录保存的是你的目录下<strong>所有文件的快照</strong>，就像是把整个目录复制，然后再粘贴一样，但比复制粘贴优雅许多！</p>
<p>Git 希望提交记录<strong>尽可能地轻量</strong>，因此在你每次进行提交时，它并不会盲目地复制整个目录。条件允许的情况下，它<strong>会将当前版本与仓库中的上一个版本进行对比，并把所有的差异打包到一起作为一个提交记录</strong>。</p>
<p><strong>Git 还保存了提交的历史记录。</strong>这也是为什么大多数提交记录的上面都有<strong>父节点的原因 —— 我们会在图示中用箭头来表示这种关系</strong>。对于项目组的成员来说，维护提交历史对大家都有好处。</p>
<p>可以把提交记录看作是项目的快照。提交记录非常轻量，可以快速地在这些提交记录之间切换！</p>
<h3 id="2-Git-Branch-Git-Switch"><a href="#2-Git-Branch-Git-Switch" class="headerlink" title="2 Git Branch/Git Switch"></a>2 Git Branch/Git Switch</h3><p><strong>Git 的分支也非常轻量。它们只是简单地指向某个提交纪录</strong> —— 仅此而已。所以许多 Git 爱好者传颂：</p>
<p><em><strong>早建分支！多用分支！</strong></em></p>
<p><strong>这是因为即使创建再多的分支也不会造成储存或内存上的开销，并且按逻辑分解工作到不同的分支要比维护那些特别臃肿的分支简单多了。</strong></p>
<p>在将分支和提交记录结合起来后，我们会看到两者如何协作。现在只要记住<strong>使用分支其实就相当于在说：“<em>我想基于这个提交以及它所有的父提交进行新的工作</em>。”</strong></p>
<ul>
<li>创建一个新的名为newImage的分支：</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git branch newImage</span><br></pre></td></tr></table></figure>

<ul>
<li>然后，直接使用git commit 并不会使得newImage分支前进，而是会使当前分支（main分支）前进，使用git checkout <name> 可以让我们在提交修改之前先切换到新的分支上。</name></li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git checkout newImage;</span><br><span class="line">git commit</span><br></pre></td></tr></table></figure>

<p><em>注意：在 Git 2.23 版本中，引入了一个名为 <code>git switch</code> 的新命令，最终会取代 <code>git checkout</code>，因为 <code>checkout</code> 作为单个命令有点超载（它承载了很多独立的功能）。可以从<a target="_blank" rel="noopener" href="https://git-scm.com/docs/git-switch">这里</a>学到<code>git switch</code> 的内容。</em></p>
<ul>
<li>有个更简洁的方式：如果你<strong>想创建一个新的分支同时切换到新创建的分支的话</strong>，可以通过 <code>git checkout -b &lt;your-branch-name&gt;</code> 来实现。</li>
</ul>
<h3 id="3-Git-merge（合并分支）"><a href="#3-Git-merge（合并分支）" class="headerlink" title="3 Git merge（合并分支）"></a>3 Git merge（合并分支）</h3><p>接下来咱们看看如何将两个分支合并到一起。就是说我们新建一个分支，在其上开发某个新功能，开发完成后再合并回主线。</p>
<p>咱们先来看一下第一种方法 —— <code>git merge</code>。<strong>在 Git 中合并两个分支时会产生一个特殊的提交记录，它有两个父节点。翻译成自然语言相当于：“我要把这两个父节点本身及它们所有的祖先都包含进来。”</strong></p>
<p>通过图示更容易理解一些:</p>
<ul>
<li>我们准备了两个分支，每个分支上各有一个独有的提交。这意味着没有一个分支包含了我们修改的所有内容。咱们通过合并这两个分支来解决这个问题。</li>
<li>我们要把 <code>bugFix</code> 合并到 <code>main</code> 里，使用：</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git merge bugFix;//当前分支为main</span><br></pre></td></tr></table></figure>

<ul>
<li>首先，<code>main</code> 现在指向了一个拥有两个父节点的提交记录。假如从 <code>main</code> 开始沿着箭头向上看，在到达起点的路上会经过所有的提交记录。这意味着 <code>main</code> 包含了对代码库的所有修改：</li>
</ul>
<img src="/2022/04/18/Learn-Git-Branching/gitmerge-16502851695131.png" alt="gitmerge" style="zoom:50%;">

<ul>
<li><p>接着，再把 <code>main</code> 分支合并到 <code>bugFix</code>：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git checkout bugFix;</span><br><span class="line">git merge main;</span><br></pre></td></tr></table></figure>

<ul>
<li>因为 <code>main</code> 继承自 <code>bugFix</code>，Git 什么都不用做，只是简单地把 <code>bugFix</code> 移动到 <code>main</code> 所指向的那个提交记录。现在所有提交记录的颜色都一样了，这表明每一个分支都包含了代码库的所有修改！大功告成！</li>
</ul>
</li>
</ul>
<h3 id="4-Git-Rebase（重定位）"><a href="#4-Git-Rebase（重定位）" class="headerlink" title="4 Git Rebase（重定位）"></a>4 Git Rebase（重定位）</h3><p>第二种合并分支的方法是 <code>git rebase</code>。<strong>Rebase 实际上就是取出一系列的提交记录，“复制”它们，然后在另外一个地方逐个的放下去。</strong></p>
<p><strong>Rebase 的优势就是可以创造更线性的提交历史，这听上去有些难以理解。如果只允许使用 Rebase 的话，代码库的提交历史将会变得异常清晰。</strong></p>
<p>git rebase后如果跟有一个参数，则说明是将当前分支的提交复制到所指位置，如</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git rebase base1</span><br></pre></td></tr></table></figure>

<p>git rebase后如果跟有两个参数，则说明是将第二个参数所在分支的提交复制到第一个参数的位置，如</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git rebase o/main base2</span><br></pre></td></tr></table></figure>

<p>实验如下：</p>
<ul>
<li>还是准备了两个分支；注意当前所在的分支是 bugFix（星号标识的是当前分支）</li>
</ul>
<img src="/2022/04/18/Learn-Git-Branching/gitrebase-16502851695133.png" alt="gitrebase" style="zoom:50%;">

<ul>
<li><p>我们想要把 bugFix 分支里的工作直接移到 main 分支上。移动以后会使得两个分支的功能看起来像是按顺序开发，但实际上它们是并行开发的。</p>
</li>
<li><p>这就需要用 <code>git rebase</code> 实现此目标</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git rebase main</span><br></pre></td></tr></table></figure>

<img src="/2022/04/18/Learn-Git-Branching/gitrebase2-16502851695134.png" alt="gitrebase2" style="zoom:50%;"></li>
<li><p>现在 bugFix 分支上的工作在 main 的最顶端，同时我们也得到了一个更线性的提交序列。</p>
</li>
<li><p>注意，提交记录 C3 依然存在（树上那个半透明的节点），而 C3’ 是我们 Rebase 到 main 分支上的 C3 的副本。</p>
</li>
<li><p>现在唯一的问题就是 main 还没有更新，下面咱们就来更新它：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git checkout main</span><br><span class="line">git rebase bugFix</span><br></pre></td></tr></table></figure>

<ul>
<li>由于 <code>bugFix</code> 继承自 <code>main</code>，所以 Git 只是简单的把 <code>main</code> 分支的引用向前移动了一下而已。</li>
</ul>
</li>
</ul>
<h2 id="高级篇"><a href="#高级篇" class="headerlink" title="高级篇"></a>高级篇</h2><h3 id="1-分离HEAD"><a href="#1-分离HEAD" class="headerlink" title="1 分离HEAD"></a>1 分离HEAD</h3><ul>
<li>HEAD 是一个<strong>对当前检出记录的符号引用</strong> —— 也就是指向你正在其基础上进行工作的提交记录。</li>
<li>HEAD <strong>总是指向当前分支上最近一次提交记录</strong>。大多数修改提交树的 Git 命令都是从改变 HEAD 的指向开始的。</li>
<li>HEAD 通常情况下是指向分支名的（如 bugFix）。在你提交时，改变了 bugFix 的状态，这一变化通过 HEAD 变得可见。</li>
<li>如果想看 HEAD 指向，可以通过 <code>cat .git/HEAD</code> 查看， 如果 HEAD 指向的是一个引用，还可以用 <code>git symbolic-ref HEAD</code> 查看它的指向。</li>
</ul>
<h4 id="分离的HEAD"><a href="#分离的HEAD" class="headerlink" title="分离的HEAD"></a>分离的HEAD</h4><ul>
<li><p>分离的 HEAD 就是让其指向了某个具体的提交记录而不是分支名。在命令执行之前的状态如下所示：</p>
<img src="/2022/04/18/Learn-Git-Branching/head-16502851695132.png" alt="head" style="zoom:50%;"></li>
</ul>
<p>​        HEAD -&gt; main -&gt; C1</p>
<p>​        即：HEAD 指向 main， main 指向 C1</p>
<ul>
<li><p>执行命令</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout C1</span><br></pre></td></tr></table></figure></li>
<li><p>现在变成了HEAD -&gt; C1  ：</p>
<img src="/2022/04/18/Learn-Git-Branching/head2-16502851695136.png" alt="head2" style="zoom:50%;"></li>
</ul>
<h3 id="2-相对引用"><a href="#2-相对引用" class="headerlink" title="2 相对引用"></a>2 相对引用</h3><ul>
<li><p>通过指定提交记录哈希值的方式在 Git 中移动不太方便。在实际应用时，并没有像本程序中这么漂亮的可视化提交树供你参考，<strong>所以你就不得不用 <code>git log</code> 来查查看提交记录的哈希值。</strong></p>
</li>
<li><p>并且哈希值在真实的 Git 世界中也会更长（注：基于 SHA-1，共 40 位）。例如前一关的介绍中的提交记录的哈希值可能是 <code>fed2da64c0efc5293610bdd892f82a58e8cbc5d8</code>。舌头都快打结了吧…</p>
</li>
<li><p>比较令人欣慰的是，Git 对哈希的处理很智能。<strong>你只需要提供能够唯一标识提交记录的前几个字符即可</strong>。因此我可以仅输入<code>fed2</code> 而不是上面的一长串字符。</p>
</li>
<li><p>正如前面所说，通过哈希值指定提交记录很不方便，所以 Git 引入了相对引用。这个就很厉害了!</p>
<p><strong>使用相对引用的话，你就可以从一个易于记忆的地方（比如 <code>bugFix</code> 分支或 <code>HEAD</code>）开始计算。</strong></p>
<p><strong>相对引用非常给力，这里我介绍两个简单的用法：</strong></p>
<ul>
<li><strong>使用 <code>^</code> 向上移动 1 个提交记录</strong></li>
<li><strong>使用 <code>~&lt;num&gt;</code> 向上移动多个提交记录，如 <code>~3</code></strong></li>
</ul>
</li>
</ul>
<h4 id="操作符"><a href="#操作符" class="headerlink" title="^操作符"></a>^操作符</h4><ul>
<li><p><strong>首先看看操作符 (^)。</strong>把这个符号加在引用名称的后面，表示让 Git 寻找指定提交记录的父提交。</p>
<ul>
<li><p>所以 <code>main^</code> 相当于“<code>main</code> 的父节点”。</p>
</li>
<li><p><code>main^^</code> 是 <code>main</code> 的第二个父节点</p>
</li>
</ul>
</li>
</ul>
<ul>
<li><p>使用命令</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout main^</span><br></pre></td></tr></table></figure>

<p>就将HEAD切换到main的父节点上了，如下图所示：</p>
<img src="/2022/04/18/Learn-Git-Branching/head3-16502851695135.png" alt="head3" style="zoom:50%;"></li>
<li><p>你也可以将 <code>HEAD</code> 作为相对引用的参照，如：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">git checkout C3;</span><br><span class="line">git checkout HEAD^;</span><br><span class="line">git checkout HEAD^;</span><br><span class="line">git checkout HEAD^;//多次使用HEAD^向上移动</span><br></pre></td></tr></table></figure>

<img src="/2022/04/18/Learn-Git-Branching/head4-16502851695139.png" alt="head4" style="zoom:50%;"></li>
</ul>
<h4 id="“-”操作符"><a href="#“-”操作符" class="headerlink" title="“~”操作符"></a>“~”操作符</h4><ul>
<li><p>如果你想在提交树中向上移动很多步的话，敲那么多 <code>^</code> 貌似也挺烦人的，Git 当然也考虑到了这一点，于是又引入了操作符 <code>~</code>。</p>
</li>
<li><p>该操作符后面可以跟一个数字（可选，不跟数字时与 <code>^</code> 相同，向上移动一次），指定向上移动多少次。</p>
</li>
<li><p>比如使用</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout HEAD~4</span><br></pre></td></tr></table></figure>

<p>效果如下：</p>
<img src="/2022/04/18/Learn-Git-Branching/head5-16502851695137.png" alt="head5" style="zoom:50%;"></li>
</ul>
<h4 id="强制修改分支位置"><a href="#强制修改分支位置" class="headerlink" title="强制修改分支位置"></a>强制修改分支位置</h4><ul>
<li><strong>使用相对引用最多的就是移动分支。</strong>可以直接使用 <code>-f</code> 选项让分支指向另一个提交。例如:</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git branch -f main HEAD~3</span><br></pre></td></tr></table></figure>

<ul>
<li><p>上面的命令会将 main 分支强制指向 HEAD 的第 3 级父提交。</p>
</li>
<li><p>相对引用为我们提供了一种简洁的引用提交记录 <code>C1</code> 的方式， 而 <code>-f</code> 则容许我们将分支强制移动到那个位置。</p>
</li>
</ul>
<h3 id="3-撤销变更"><a href="#3-撤销变更" class="headerlink" title="3 撤销变更"></a>3 撤销变更</h3><p>在 Git 里撤销变更的方法很多。和提交一样，<strong>撤销变更由底层部分（暂存区的独立文件或者片段）和上层部分（变更到底是通过哪种方式被撤销的）组成</strong>。我们这个应用主要关注的是后者。</p>
<p>主要有两种方法用来撤销变更 —— 一是 <code>git reset</code>，还有就是 <code>git revert</code>。</p>
<h4 id="Git-Reset（本地撤销）"><a href="#Git-Reset（本地撤销）" class="headerlink" title="Git Reset（本地撤销）"></a>Git Reset（本地撤销）</h4><ul>
<li><p><strong><code>git reset</code> 通过把分支记录回退几个提交记录来实现撤销改动。</strong>你可以将这想象成“改写历史”。<code>git reset</code> 向上移动分支，原来指向的提交记录就跟从来没有提交过一样。</p>
</li>
<li><p>比如使用</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git reset HEAD~1</span><br></pre></td></tr></table></figure>

<p>Git 就把 main 分支移回到 <code>C1</code>；现在我们的本地代码库根本就不知道有 <code>C2</code> 这个提交了。</p>
<p>（译者注：在reset后， <code>C2</code> 所做的变更还在，但是处于未加入暂存区状态。）</p>
</li>
</ul>
<h4 id="Git-Revert（远程撤销）"><a href="#Git-Revert（远程撤销）" class="headerlink" title="Git Revert（远程撤销）"></a>Git Revert（远程撤销）</h4><ul>
<li><p>虽然在你的<strong>本地分支中使用 <code>git reset</code></strong> 很方便，但是这种“改写历史”的方法对大家一起使用的远程分支是无效的哦！</p>
</li>
<li><p>**为了撤销更改并分享给别人，我们需要使用 <code>git revert</code>**。来看演示：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git revert HEAD</span><br></pre></td></tr></table></figure>

<p>效果如下：</p>
<img src="/2022/04/18/Learn-Git-Branching/revert-16502851695138.png" alt="revert" style="zoom:50%;"></li>
<li><p>注意，使用git revert后，在我们要撤销的提交记录后面多了一个新提交！这是因为新提交记录 <code>C2&#39;</code> 引入了<strong>更改</strong> —— 这些更改刚好是用来撤销 <code>C2</code> 这个提交的。也就是说 <code>C2&#39;</code> 的状态与 <code>C1</code> 是相同的。</p>
</li>
<li><p>revert 之后就可以把你的更改推送到远程仓库与别人分享啦。</p>
</li>
</ul>
<h2 id="移动提交记录"><a href="#移动提交记录" class="headerlink" title="移动提交记录"></a>移动提交记录</h2><p>到现在我们已经学习了 Git 的基础知识 —— 提交、分支以及在提交树上移动。 这些概念涵盖了 Git 90% 的功能，同样也足够满足开发者的日常需求</p>
<p>然而, <strong>剩余的 10% 在处理复杂的工作流时(或者当你陷入困惑时）可能就显得尤为重要了</strong>。接下来要讨论的这个话题是“整理提交记录” —— 开发人员有时会说“我想要把这个提交放到这里, 那个提交放到刚才那个提交的后面”, 而接下来就讲的就是它的实现方式，非常清晰、灵活，还很生动。</p>
<h3 id="1-Git-Cherry-pick（抓取任意提交）"><a href="#1-Git-Cherry-pick（抓取任意提交）" class="headerlink" title="1 Git Cherry-pick（抓取任意提交）"></a>1 Git Cherry-pick（抓取任意提交）</h3><p>本系列的第一个命令是 <code>git cherry-pick</code>, 命令形式为:</p>
<ul>
<li><code>git cherry-pick &lt;提交号&gt;...</code></li>
</ul>
<p><strong>如果你想将一些提交复制到当前所在的位置（<code>HEAD</code>）下面的话， Cherry-pick 是最直接的方式了。</strong></p>
<p>咱们还是通过例子来看一下！ </p>
<ul>
<li><p>这里有一个仓库, 我们想将 <code>side</code> 分支上的工作复制到 <code>main</code> 分支，你立刻想到了之前学过的 <code>rebase</code> 了吧？但是咱们还是看看 <code>cherry-pick</code> 有什么本领吧。</p>
</li>
<li><img src="/2022/04/18/Learn-Git-Branching/cherry-165028516951311.png" alt="cherry" style="zoom:50%;"></li>
<li><p>使用命令</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git cherry-pick C2 C4</span><br></pre></td></tr></table></figure>

<p>我们只需要提交记录 <code>C2</code> 和 <code>C4</code>，上面这条命令让 Git 将它们抓过来放到当前分支下了。</p>
<img src="/2022/04/18/Learn-Git-Branching/rebase3-165028516951310.png" alt="rebase3" style="zoom:50%;"></li>
</ul>
<h3 id="2-交互式的-rebase"><a href="#2-交互式的-rebase" class="headerlink" title="2 交互式的 rebase"></a>2 交互式的 rebase</h3><ul>
<li><p>当你知道你所需要的提交记录（<strong>并且</strong>还知道这些提交记录的哈希值）时, 用 cherry-pick 再好不过了 —— 没有比这更简单的方式了。</p>
</li>
<li><p>但是如果你不清楚你想要的提交记录的哈希值呢? 幸好 Git 帮你想到了这一点, 我们可以<strong>利用交互式的 rebase —— 如果你想从一系列的提交记录中找到想要的记录, 这就是最好的方法了</strong>。</p>
</li>
<li><p><strong>交互式 rebase 指的是使用带参数 <code>--interactive</code> 的 rebase 命 令, 简写为 <code>-i</code></strong></p>
</li>
<li><p><strong>如果你在命令后增加了这个选项, Git 会打开一个 UI 界面 并列出将要被复制到目标分支的备选提交记录，它还会显示每个提交记录的哈希值和提交说明，提交说明有助于你理解这个提交进行了哪些更改。</strong></p>
</li>
<li><p>在实际使用时，所谓的 UI 窗口一般会在文本编辑器 —— 如 Vim —— 中打开一个文件。</p>
</li>
<li><p>当 rebase UI界面打开时, 你能做3件事:</p>
<ul>
<li>调整提交记录的顺序（通过鼠标拖放来完成）</li>
<li>删除你不想要的提交（通过切换 <code>pick</code> 的状态来完成，关闭就意味着你不想要这个提交记录）</li>
<li>合并提交。简而言之，它允许你把多个提交记录合并成一个。</li>
</ul>
</li>
</ul>
<p>接下来咱们看个实例：</p>
<ul>
<li><img src="/2022/04/18/Learn-Git-Branching/rebase-165028516951312.png" alt="rebase" style="zoom:50%;"></li>
<li><p>使用命令</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git rebase -i HEAD~4</span><br></pre></td></tr></table></figure>

<p>会出现一个交互对话框。对提交记录做个排序（当然你也可以删除某些提交）：</p>
<img src="/2022/04/18/Learn-Git-Branching/rebase2-165028516951313.png" alt="rebase2" style="zoom:50%;"></li>
<li><p>Git将严格按照你在对话框中指定的方式进行了复制。</p>
</li>
</ul>
<h2 id="杂项（技巧与贴士）"><a href="#杂项（技巧与贴士）" class="headerlink" title="杂项（技巧与贴士）"></a>杂项（技巧与贴士）</h2><h3 id="1-本地栈式提交"><a href="#1-本地栈式提交" class="headerlink" title="1 本地栈式提交"></a>1 本地栈式提交</h3><ul>
<li><p>来看一个在开发中经常会遇到的情况：我正在解决某个特别棘手的 Bug，为了便于调试而在代码中添加了一些调试命令并向控制台打印了一些信息。</p>
</li>
<li><p><strong>这些调试和打印语句都在它们各自的提交记录里。</strong>最后我终于找到了造成这个 Bug 的根本原因，解决掉以后觉得沾沾自喜！</p>
</li>
<li><p><strong>最后就差把 <code>bugFix</code> 分支里的工作合并回 <code>main</code> 分支了。</strong>你可以选择通过 fast-forward 快速合并到 <code>main</code> 分支上，但这样的话 <code>main</code> 分支就会包含我这些调试语句了。你肯定不想这样，应该还有更好的方式……</p>
</li>
<li><p>实际我们只要让 Git 复制解决问题的那一个提交记录就可以了。跟之前我们在“整理提交记录”中学到的一样，我们可以使用</p>
</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git rebase -i</span><br><span class="line">git cherry-pick</span><br></pre></td></tr></table></figure>

<p>​       来达到目的。</p>
<h3 id="2-提交的技巧"><a href="#2-提交的技巧" class="headerlink" title="2 提交的技巧"></a>2 提交的技巧</h3><ul>
<li>接下来这种情况也是很常见的：你之前在 <code>newImage</code> 分支上进行了一次提交，然后又基于它创建了 <code>caption</code> 分支，然后又提交了一次。</li>
<li>此时你想对某个以前的提交记录进行一些小小的调整。比如设计师想修改一下 <code>newImage</code> 中图片的分辨率，尽管那个提交记录并不是最新的了。</li>
<li>我们可以通过下面的方法来克服困难：<ul>
<li>先用 <code>git rebase -i</code> 将提交重新排序，然后把我们想要修改的提交记录挪到最前</li>
<li>然后用 <code>git commit --amend</code> 来进行一些小修改</li>
<li>接着再用 <code>git rebase -i</code> 来将他们调回原来的顺序</li>
<li>最后我们把 main 移到修改的最前端（用你自己喜欢的方法），就大功告成啦！</li>
</ul>
</li>
</ul>
<h3 id="3-提交的技巧2"><a href="#3-提交的技巧2" class="headerlink" title="3 提交的技巧2"></a>3 提交的技巧2</h3><ul>
<li><p>正如你在上一节所见到的，我们可以使用 <code>rebase -i</code> 对提交记录进行重新排序。只要把我们想要的提交记录挪到最前端，我们就可以很轻松的用 <code>--amend</code> 修改它，然后把它们重新排成我们想要的顺序。</p>
</li>
<li><p>但这样做就唯一的问题就是要进行两次排序，而这<strong>有可能造成由 rebase 而导致的冲突</strong>。下面还是看看 <code>git cherry-pick</code> 是怎么做的吧。</p>
</li>
<li><p><strong>要在心里牢记 cherry-pick 可以将提交树上任何地方的提交记录取过来追加到 HEAD 上（只要不是 HEAD 上游的提交就没问题）。</strong></p>
</li>
</ul>
<h3 id="4-Git-Tags（打标签）"><a href="#4-Git-Tags（打标签）" class="headerlink" title="4 Git Tags（打标签）"></a>4 Git Tags（打标签）</h3><ul>
<li>相信通过前面课程的学习你已经发现了：分支很容易被人为移动，并且当有新的提交时，它也会移动。分支很容易被改变，大部分分支还只是临时的，并且还一直在变。</li>
<li>你可能会问了：有没有什么可以<strong>永远</strong>指向某个提交记录的标识呢，比如软件发布新的大版本，或者是修正一些重要的 Bug 或是增加了某些新特性，有没有比分支更好的可以永远指向这些提交的方法呢？</li>
<li>当然有了！那就是<strong>Git tag，它们可以（在某种程度上 —— 因为标签可以被删除后重新在另外一个位置创建同名的标签）永久地将某个特定的提交命名为里程碑，然后就可以像分支一样引用了</strong>。</li>
<li>更难得的是，它们并不会随着新的提交而移动。你也不能切换到某个标签上面进行修改提交，<strong>它就像是提交树上的一个锚点，标识了某个特定的位置。</strong></li>
</ul>
<p>咱们来看看标签到底是什么样：</p>
<ul>
<li><p>咱们先建立一个标签，指向提交记录 <code>C1</code>，表示这是我们 1.0 版本。</p>
<ul>
<li><p>使用</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git tag v1 C1</span><br></pre></td></tr></table></figure>

<p>将这个标签命名为 <code>v1</code>，并且明确地让它指向提交记录 <code>C1</code>，如果你不指定提交记录，Git 会用 <code>HEAD</code> 所指向的位置。</p>
 <img src="/2022/04/18/Learn-Git-Branching/tag-165028516951314.png" alt="tag" style="zoom:50%;"></li>
</ul>
</li>
</ul>
<h3 id="5-Git-Describe（找最近版本）"><a href="#5-Git-Describe（找最近版本）" class="headerlink" title="5 Git Describe（找最近版本）"></a>5 Git Describe（找最近版本）</h3><ul>
<li><p>由于<strong>标签在代码库中起着“锚点”的作用</strong>，Git 还为此专门设计了一个命令用来<strong>描述</strong>离你最近的锚点（也就是标签），它就是 <code>git describe</code>！</p>
</li>
<li><p><strong>Git Describe 能帮你在提交历史中移动了多次以后找到方向</strong>；当你用 <code>git bisect</code>（一个查找产生 Bug 的提交记录的指令）找到某个提交记录时，或者是当你坐在你那刚刚度假回来的同事的电脑前时， 可能会用到这个命令。</p>
</li>
<li><p><code>git describe</code> 的语法是：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git describe &lt;ref&gt;</span><br></pre></td></tr></table></figure>

<p><code>&lt;ref&gt;</code> 可以是任何能被 Git 识别成提交记录的引用，如果你没有指定的话，Git 会以你目前所检出的位置（<code>HEAD</code>）。</p>
<p>它输出的结果是这样的：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;tag&gt;_&lt;numCommits&gt;_g&lt;hash&gt;</span><br></pre></td></tr></table></figure>

<p><code>tag</code> 表示的是离 <code>ref</code> 最近的标签， <code>numCommits</code> 是表示这个 <code>ref</code> 与 <code>tag</code> 相差有多少个提交记录， <code>hash</code> 表示的是你所给定的 <code>ref</code> 所表示的提交记录哈希值的前几位。</p>
<p>当 <code>ref</code> 提交记录上有某个标签时，则只输出标签名称。</p>
</li>
<li><p>让我们来看一个例子，对于下面的提交树：</p>
<img src="/2022/04/18/Learn-Git-Branching/describe-165028516951315.png" alt="describe" style="zoom:50%;"></li>
</ul>
<p><code>git describe main</code> 会输出：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">v1_2_gC2</span><br></pre></td></tr></table></figure>

<p><code>git describe side</code> 会输出：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">v2_1_gC4</span><br></pre></td></tr></table></figure>

<h2 id="高级话题"><a href="#高级话题" class="headerlink" title="高级话题"></a>高级话题</h2><h3 id="1-多次Rebase"><a href="#1-多次Rebase" class="headerlink" title="1 多次Rebase"></a>1 多次Rebase</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git rebase another main</span><br></pre></td></tr></table></figure>

<p>表示将main分支rebase 到another分支。</p>
<h3 id="2-两个父节点"><a href="#2-两个父节点" class="headerlink" title="2 两个父节点"></a>2 两个父节点</h3><ul>
<li><p>操作符 <code>^</code> 与 <code>~</code> 符一样，后面也可以跟一个数字。</p>
</li>
<li><p>但是该操作符后面的数字与 <code>~</code> 后面的不同，并不是用来指定向上返回几代，而是<strong>指定合并提交记录的某个父提交。</strong>还记得前面提到过的一个合并提交有两个父提交吧，所以遇到这样的节点时该选择哪条路径就不是很清晰了。</p>
</li>
<li><p>Git 默认选择合并提交的“第一个”父提交，在操作符 <code>^</code> 后跟一个数字可以改变这一默认行为。</p>
</li>
</ul>
<p>举个例子：</p>
<ul>
<li><p>这里有一个合并提交记录。如果不加数字修改符直接检出 <code>main^</code>，会回到第一个父提交记录。(<em>在我们的图示中，第一个父提交记录是指合并提交记录正上方的那个提交记录。</em>)</p>
<img src="/2022/04/18/Learn-Git-Branching/twofather-165028516951316.png" alt="twofather" style="zoom:50%;"></li>
<li><p>使用命令</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout main^</span><br></pre></td></tr></table></figure>

<img src="/2022/04/18/Learn-Git-Branching/twofather2-165028516951317.png" alt="twofather2" style="zoom:50%;"></li>
<li><p>使用命令</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout main^2</span><br></pre></td></tr></table></figure>

<img src="/2022/04/18/Learn-Git-Branching/twofather3-165028516951318.png" alt="twofather3" style="zoom:50%;"></li>
<li><p>这样就回到了另一个父提交上。</p>
</li>
<li><p>使用 <code>^</code> 和 <code>~</code> 可以自由地在提交树中移动，非常给力：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git checkout HEAD~;</span><br><span class="line">git checkout HEAD^2;//选择第二个父分支</span><br><span class="line">git checkout HEAD~2//在当前分支上向上移动两次提交</span><br></pre></td></tr></table></figure></li>
<li><p>这些操作符还支持链式操作</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout HEAD~^2~2</span><br></pre></td></tr></table></figure>

<p>效果同上。</p>
</li>
</ul>
<h1 id="Learn-Git-Branching（远程部分）"><a href="#Learn-Git-Branching（远程部分）" class="headerlink" title="Learn Git Branching（远程部分）"></a>Learn Git Branching（远程部分）</h1><p>远程仓库只是你的仓库在另个一台计算机上的拷贝。你可以通过因特网与这台计算机通信 —— 也就是增加或是获取提交记录。</p>
<p>话虽如此, 远程仓库却有一系列强大的特性</p>
<ul>
<li>首先也是最重要的的点, 远程仓库是一个强大的备份。本地仓库也有恢复文件到指定版本的能力, 但所有的信息都是保存在本地的。有了远程仓库以后，即使丢失了本地所有数据, 你仍可以通过远程仓库拿回你丢失的数据。</li>
<li>还有就是, 远程让代码社交化了! 既然你的项目被托管到别的地方了, 你的朋友可以更容易地为你的项目做贡献(或者拉取最新的变更)</li>
</ul>
<p>现在用网站来对远程仓库进行可视化操作变得越发流行了(像 <a target="_blank" rel="noopener" href="https://github.com/">GitHub</a> 或 <a target="_blank" rel="noopener" href="http://phabricator.org/">Phabricator</a>), 但远程仓库<strong>永远</strong>是这些工具的顶梁柱, 因此理解其概念非常的重要!</p>
<h2 id="Push-amp-Pull"><a href="#Push-amp-Pull" class="headerlink" title="Push&amp;Pull"></a>Push&amp;Pull</h2><h3 id="1-Git-clone"><a href="#1-Git-clone" class="headerlink" title="1 Git clone"></a>1 Git clone</h3><ul>
<li>从技术上来讲，<code>git clone</code> 命令在真实的环境下的作用是在<strong>本地</strong>创建一个远程仓库的拷贝（比如从 github.com）。 （当然，它也可以在远程创建一个你本地仓库的副本。）</li>
</ul>
<h3 id="2-远程分支"><a href="#2-远程分支" class="headerlink" title="2 远程分支"></a>2 远程分支</h3><p><strong>远程分支指的是在本地git中的与远程仓库关联的分支。</strong></p>
<ul>
<li>使用git clone后，第一个事就是在我们的本地仓库多了一个名为 <code>o/main</code> 的分支, 这种类型的分支就叫<strong>远程</strong>分支。由于远程分支的特性导致其拥有一些特殊属性。</li>
<li>远程分支反映了远程仓库(在你上次和它通信时)的<strong>状态</strong>。这会有助于你理解本地的工作与公共工作的差别 —— 这是你与别人分享工作成果前至关重要的一步.</li>
<li>远程分支有一个特别的属性，在你检出时自动进入分离 HEAD 状态。Git 这么做是出于不能直接在这些分支上进行操作的原因, 你必须在别的地方完成你的工作, （更新了远程分支之后）再用远程分享你的工作成果。</li>
</ul>
<p><strong>为什么有 <code>o/</code>？</strong></p>
<ul>
<li>你可能想问这些远程分支的前面的 <code>o/</code> 是什么意思呢？好吧, 远程分支有一个命名规范 —— 它们的格式是:</li>
</ul>
<p>​        <code>&lt;remote name&gt;/&lt;branch name&gt;</code></p>
<ul>
<li>因此，如果你看到一个名为 <code>o/main</code> 的分支，那么这个分支就叫 <code>main</code>，远程仓库的名称就是 <code>o</code>。</li>
<li>大多数的开发人员会将它们主要的远程仓库命名为 <code>origin</code>，并不是 <code>o</code>。这是因为当你用 <code>git clone</code> 某个仓库时，Git 已经帮你把远程仓库的名称设置为 <code>origin</code> 了</li>
</ul>
<p>不过 <code>origin</code> 对于我们的 UI 来说太长了，因此不得不使用简写 <code>o</code> 但是要记住, 当你使用真正的 Git 时, 你的远程仓库默认为 <code>origin</code>!</p>
<p><strong>如果检出远程分支会怎么样呢？</strong></p>
<ul>
<li><p>使用</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git checkout origin/main;</span><br><span class="line">git commit;</span><br></pre></td></tr></table></figure></li>
<li><p>GIt变成了分离 HEAD 状态，当添加新的提交时 <code>o/main</code> 也不会更新。这是因为 <code>o/main</code> 只有在远程仓库中相应的分支更新了以后才会更新。</p>
</li>
</ul>
<h3 id="3-Git-Fetch"><a href="#3-Git-Fetch" class="headerlink" title="3 Git Fetch"></a>3 Git Fetch</h3><ul>
<li><p><strong>Git 远程仓库相当的操作实际可以归纳为两点：向远程仓库传输数据以及从远程仓库获取数据。</strong>既然我们能与远程仓库同步，那么就可以分享任何能被 Git 管理的更新（因此可以分享代码、文件、想法、情书等等）。</p>
</li>
<li><p>本节课我们将学习<strong>如何从远程仓库获取数据</strong> —— 命令如其名，它就是 <code>git fetch</code>。</p>
</li>
</ul>
<hr>
<p><strong>git fetch 做了些什么</strong></p>
<ul>
<li><p><code>git fetch</code> 完成了仅有的但是很重要的两步:</p>
<ul>
<li><p>从远程仓库下载本地仓库中缺失的提交记录</p>
</li>
<li><p>更新远程分支指针(如 <code>o/main</code>)</p>
</li>
</ul>
</li>
<li><p><strong><code>git fetch</code> 实际上将本地仓库中的远程分支更新成了远程仓库相应分支最新的状态。</strong></p>
</li>
<li><p>如果你还记得上一节课程中我们说过的，远程分支反映了远程仓库在你<strong>最后一次与它通信时</strong>的状态，<code>git fetch</code> 就是你与远程仓库通信的方式了！希望我说的够明白了，你已经了解 <code>git fetch</code> 与远程分支之间的关系了吧。</p>
</li>
<li><p><code>git fetch</code> 通常通过互联网（使用 <code>http://</code> 或 <code>git://</code> 协议) 与远程仓库通信。</p>
</li>
</ul>
<p><strong>git fetch 不会做的事</strong></p>
<ul>
<li><strong><code>git fetch</code> 并不会改变你本地仓库的状态。它不会更新你的 <code>main</code> 分支，也不会修改你磁盘上的文件。</strong></li>
<li>理解这一点很重要，因为许多开发人员误以为执行了 <code>git fetch</code> 以后，他们本地仓库就与远程仓库同步了。它可能已经将进行这一操作所需的所有数据都下载了下来，但是<strong>并没有</strong>修改你本地的文件。</li>
</ul>
<p>所以, 你可以将 <code>git fetch</code> 的理解为单纯的下载操作。</p>
<h3 id="4-Git-Pull"><a href="#4-Git-Pull" class="headerlink" title="4 Git Pull"></a>4 Git Pull</h3><p>当远程分支中有新的提交时，你可以像合并本地分支那样来合并远程分支。也就是说就是你可以执行以下命令:</p>
<ul>
<li><code>git cherry-pick o/main</code></li>
<li><code>git rebase o/main</code></li>
<li><code>git merge o/main</code></li>
<li>等等</li>
</ul>
<p>实际上，由于先抓取更新再合并到本地分支这个流程很常用，因此 Git 提供了一个专门的命令来完成这两个操作。它就是我们要讲的 <code>git pull</code>。</p>
<p>也就是说git pull做的就是抓取更新合并的过程，即 <code>git pull</code> 就是 git fetch 和 git merge 的缩写。</p>
<h3 id="5-团队合作"><a href="#5-团队合作" class="headerlink" title="5 团队合作"></a>5 团队合作</h3><p>团队合作中，你的同事、朋友、合作伙伴更新了远程仓库，有可能是某个特定的分支，或是几个提交记录。</p>
<p>比如，你的同事在远程分支上提交了两次，这时，但是你自己的本地分支上也做一些提交，这就需要拉取远程仓库的变更。也就是git pull，这样的操作会让你的本地分支抓取远程分支的更新并合并。</p>
<h3 id="6-Git-Push"><a href="#6-Git-Push" class="headerlink" title="6 Git Push"></a>6 Git Push</h3><p>上传自己分享内容与下载他人的分享刚好相反，那与 <code>git pull</code> 相反的命令是什么呢？<code>git push</code>！</p>
<p><code>git push</code> 负责将<strong>你的</strong>变更上传到指定的远程仓库，并在远程仓库上合并你的新提交记录。一旦 <code>git push</code> 完成, 你的朋友们就可以从这个远程仓库下载你分享的成果了！</p>
<p>你可以将 <code>git push</code> 想象成发布你成果的命令。</p>
<p><em>注意 —— <code>git push</code> 不带任何参数时的行为与 Git 的一个名为 <code>push.default</code> 的配置有关。它的默认值取决于你正使用的 Git 的版本，在你的项目中进行推送之前，最好检查一下这个配置。</em></p>
<h3 id="7-偏离的工作"><a href="#7-偏离的工作" class="headerlink" title="7 偏离的工作"></a>7 偏离的工作</h3><p>假设你周一克隆了一个仓库，然后开始研发某个新功能。到周五时，你新功能开发测试完毕，可以发布了。但是 —— 天啊！你的同事这周写了一堆代码，还改了许多你的功能中使用的 API，这些变动会导致你新开发的功能变得不可用。但是他们已经将那些提交推送到远程仓库了，因此你的工作就变成了基于项目<strong>旧版</strong>的代码，与远程仓库最新的代码不匹配了。</p>
<p>这种情况下, <code>git push</code> 就不知道该如何操作了。如果你执行 <code>git push</code>，Git 应该让远程仓库回到星期一那天的状态吗？还是直接在新代码的基础上添加你的代码，亦或由于你的提交已经过时而直接忽略你的提交？</p>
<p>因为这情况（历史偏离）有许多的不确定性，Git 是不会允许你 <code>push</code> 变更的。实际上它会<strong>强制你先合并远程最新的代码，然后才能分享你的工作。</strong></p>
<hr>
<p><strong>那该如何解决这个问题呢？很简单，你需要做的就是使你的工作基于最新的远程分支。</strong></p>
<p>有许多方法做到这一点呢，不过最直接的方法就是通过 rebase 调整你的工作。</p>
<p>如下例子：</p>
<img src="/2022/04/18/Learn-Git-Branching/偏离的push-165028516951320.png" alt="偏离的push" style="zoom:60%;">

<ul>
<li><p>在push前做rebase：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git fetch;</span><br><span class="line">git rebase o/main;</span><br><span class="line">git push</span><br></pre></td></tr></table></figure>

<img src="/2022/04/18/Learn-Git-Branching/偏离的push2-165028516951319.png" alt="偏离的push2" style="zoom:60%;"></li>
<li><p>我们用 <code>git fetch</code> 更新了本地仓库中的远程分支，然后用 rebase 将我们的工作移动到最新的提交记录下，最后再用 <code>git push</code> 推送到远程仓库。</p>
</li>
</ul>
<p>还有其它的方法可以在远程仓库变更了以后更新我的工作吗? 当然有，我们还可以使用 <code>merge</code></p>
<ul>
<li><p>尽管 <code>git merge</code> 不会移动你的工作（它会创建新的合并提交），但是它会告诉 Git 你已经合并了远程仓库的所有变更。这是因为远程分支现在是你本地分支的祖先，也就是说你的提交已经包含了远程分支的所有变化。</p>
</li>
<li><p>看下演示（也是由上上图得来）：</p>
<img src="/2022/04/18/Learn-Git-Branching/偏离的push3-165028516951321.png" alt="偏离的push3" style="zoom:60%;">

<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git fetch;</span><br><span class="line">git merge o/main;</span><br><span class="line">git push;</span><br></pre></td></tr></table></figure></li>
<li><p>我们用 <code>git fetch</code> 更新了本地仓库中的远程分支，然后<strong>合并</strong>了新变更到我们的本地分支（为了包含远程仓库的变更），最后我们用 <code>git push</code> 把工作推送到远程仓库</p>
</li>
</ul>
<p>很好！但是要敲那么多命令，有没有更简单一点的？</p>
<ul>
<li>当然 —— 前面已经介绍过 <code>git pull</code> 就是 fetch 和 merge 的简写，类似的 <strong><code>git pull --rebase</code> 就是 fetch 和 rebase 的简写！</strong></li>
</ul>
<h3 id="8-锁定的Main（Locked-Main）"><a href="#8-锁定的Main（Locked-Main）" class="headerlink" title="8 锁定的Main（Locked Main）"></a>8 锁定的Main（Locked Main）</h3><ul>
<li>如果你是在一个大的合作团队中工作, 很可能是main被锁定了, 需要一些Pull Request流程来合并修改。如果你直接提交(commit)到本地main, 然后试图推送(push)修改, 你将会收到这样类似的信息:</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">! [远程服务器拒绝] main -&gt; main (TF402455: 不允许推送(push)这个分支; 你必须使用pull request来更新这个分支.)</span><br></pre></td></tr></table></figure>

<p><strong>为什么会被拒绝?</strong></p>
<ul>
<li><p>远程服务器拒绝直接推送(push)提交到main, 因为策略配置要求 pull requests 来提交更新。</p>
</li>
<li><p>你应该按照流程,新建一个分支, 推送(push)这个分支并申请pull request,但是你忘记并直接提交给了main.现在你卡住并且无法推送你的更新。</p>
</li>
</ul>
<p><strong>解决办法：</strong></p>
<ul>
<li>新建一个分支feature, 推送到远程服务器. 然后reset你的main分支和远程服务器保持一致, 否则下次你pull并且他人的提交和你冲突的时候就会有问题.</li>
</ul>
<h2 id="Git-远程仓库高级操作"><a href="#Git-远程仓库高级操作" class="headerlink" title="Git 远程仓库高级操作"></a>Git 远程仓库高级操作</h2><h3 id="1、推送主分支"><a href="#1、推送主分支" class="headerlink" title="1、推送主分支"></a>1、推送主分支</h3><p>在大型项目中开发人员通常会在（从 <code>main</code> 上分出来的）特性分支上工作，工作完成后只做一次集成。但是有些开发人员只在 main 上做 push、pull —— 这样的话 main 总是最新的，始终与远程分支 (o/main) 保持一致。</p>
<p>对于接下来这个工作流，我们集成了两个步骤：</p>
<ul>
<li>将特性分支集成到 <code>main</code> 上</li>
<li>推送并更新远程分支</li>
</ul>
<p>例子：</p>
<ul>
<li>这里共有三个特性分支 —— <code>side1</code> <code>side2</code> 和 <code>side3</code></li>
<li>我需要将这三分支按顺序推送到远程仓库</li>
<li>因为远程仓库已经被更新过了，所以我们还要把那些工作合并过来</li>
</ul>
<p>目前状态：</p>
<img src="/2022/04/18/Learn-Git-Branching/side1-165028516951422.png" alt="side1" style="zoom:60%;">

<p>目标状态：</p>
<img src="/2022/04/18/Learn-Git-Branching/side2-165028516951423.png" alt="side2" style="zoom: 50%;">

<p>具体操作：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">git fetch</span><br><span class="line">git rebase o/main side1</span><br><span class="line">git rebase side1 side2</span><br><span class="line">git rebase side2 side3</span><br><span class="line">git rebase side3 main</span><br><span class="line">git push</span><br></pre></td></tr></table></figure>

<h3 id="2、合并远程仓库"><a href="#2、合并远程仓库" class="headerlink" title="2、合并远程仓库"></a>2、合并远程仓库</h3><p>为了 push 新变更到远程仓库，你要做的就是<strong>包含</strong>远程仓库中最新变更。意思就是只要你的本地分支包含了远程分支（如 <code>o/main</code>）中的最新变更就可以了，至于具体是用 rebase 还是 merge，并没有限制。</p>
<p>在开发社区里，有许多关于 merge 与 rebase 的讨论。以下是关于 rebase 的优缺点：</p>
<p>优点:</p>
<ul>
<li>Rebase 使你的提交树变得很干净, 所有的提交都在一条线上</li>
</ul>
<p>缺点:</p>
<ul>
<li>Rebase 修改了提交树的历史</li>
</ul>
<p>比如, 提交 C1 可以被 rebase 到 C3 之后。这看起来 C1 中的工作是在 C3 之后进行的，但实际上是在 C3 之前。</p>
<p>一些开发人员喜欢保留提交历史，因此更偏爱 merge。而其他人（比如我自己）可能更喜欢干净的提交树，于是偏爱 rebase。仁者见仁，智者见智。 </p>
<p>上面关卡中的问题如果用merge来解决，则方案如下:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">git checkout main</span><br><span class="line">git pull</span><br><span class="line">git merge side1</span><br><span class="line">git merge side2</span><br><span class="line">git merge side3</span><br><span class="line">git push</span><br></pre></td></tr></table></figure>

<p>结果如下：</p>
<img src="/2022/04/18/Learn-Git-Branching/mergepush-165028516951424.png" alt="mergepush" style="zoom:60%;">

<h3 id="3、远程跟踪分支"><a href="#3、远程跟踪分支" class="headerlink" title="3、远程跟踪分支"></a>3、远程跟踪分支</h3><p>在前几节课程中有件事儿挺神奇的，Git 好像知道 <code>main</code> 与 <code>o/main</code> 是相关的。当然这些分支的名字是相似的，可能会让你觉得是依此将远程分支 main 和本地的 main 分支进行了关联。这种关联在以下两种情况下可以清楚地得到展示：</p>
<ul>
<li>pull 操作时, 提交记录会被先下载到 o/main 上，之后再合并到本地的 main 分支。隐含的合并目标由这个关联确定的。</li>
<li>push 操作时, 我们把工作从 <code>main</code> 推到远程仓库中的 <code>main</code> 分支(同时会更新远程分支 <code>o/main</code>) 。这个推送的目的地也是由这种关联确定的！</li>
</ul>
<p>直接了当地讲，**<code>main</code> 和 <code>o/main</code> 的关联关系就是由分支的“remote tracking”属性决定的。<code>main</code> 被设定为跟踪 <code>o/main</code> —— 这意味着为 <code>main</code> 分支指定了推送的目的地以及拉取后合并的目标。**</p>
<p>你可能想知道 <code>main</code> 分支上这个属性是怎么被设定的，你并没有用任何命令指定过这个属性呀！好吧, <strong>当你克隆仓库的时候, Git 就自动帮你把这个属性设置好了。</strong></p>
<p><strong>当你克隆时, Git 会为远程仓库中的每个分支在本地仓库中创建一个远程分支（比如 <code>o/main</code>）。然后再创建一个跟踪远程仓库中活动分支的本地分支，默认情况下这个本地分支会被命名为 <code>main</code>。</strong></p>
<p>克隆完成后，你会得到一个本地分支（如果没有这个本地分支的话，你的目录就是“空白”的），但是可以查看远程仓库中所有的分支（如果你好奇心很强的话）。这样做对于本地仓库和远程仓库来说，都是最佳选择。</p>
<p>这也解释了为什么会在克隆的时候会看到下面的输出：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">local branch &quot;main&quot; set to track remote branch &quot;o/main&quot;</span><br></pre></td></tr></table></figure>

<p><strong>我能自己指定这个属性吗？</strong></p>
<p>当然可以啦！你可以让任意分支跟踪 <code>o/main</code>, 然后该分支会像 <code>main</code> 分支一样得到隐含的 push 目的地以及 merge 的目标。 这意味着你可以在分支 <code>totallyNotMain</code> 上执行 <code>git push</code>，将工作推送到远程仓库的 <code>main</code> 分支上。</p>
<p>有两种方法设置这个属性，</p>
<ul>
<li>第一种就是通过远程分支检出一个新的分支，执行:</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout -b totallyNotMain o/main</span><br></pre></td></tr></table></figure>

<p>就可以创建一个名为 <code>totallyNotMain</code> 的分支，它跟踪远程分支 <code>o/main</code>。</p>
<p>这样pull和push的远程分支o/main跟踪的本地分支就是totallyNotMain了</p>
<ul>
<li>第二种方法</li>
</ul>
<p>另一种设置远程追踪分支的方法就是使用：<code>git branch -u</code> 命令，执行：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git branch -u o/main foo</span><br></pre></td></tr></table></figure>

<p>这样 <code>foo</code> 就会跟踪 <code>o/main</code> 了。如果当前就在 foo 分支上, 还可以省略 foo：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git branch -u o/main</span><br></pre></td></tr></table></figure>

<h3 id="4、Git-Push的参数"><a href="#4、Git-Push的参数" class="headerlink" title="4、Git Push的参数"></a>4、Git Push的参数</h3><p>在远程跟踪课程中，你已经学到了 Git 是通过当前检出分支的属性来确定远程仓库以及要 push 的目的地的。这是未指定参数时的行为，我们可以为 push 指定参数，语法是：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push &lt;remote&gt; &lt;place&gt;</span><br></pre></td></tr></table></figure>

<p><code>&lt;place&gt;</code> 参数是什么意思呢？我们稍后会深入其中的细节, 先看看例子, 这个命令是:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push origin main</span><br></pre></td></tr></table></figure>

<p>把这个命令翻译过来就是：</p>
<p><em>切到本地仓库中的“main”分支，获取所有的提交，再到远程仓库“origin”中找到“main”分支，将远程仓库中没有的提交记录都添加上去，搞定之后告诉我。</em></p>
<p>我们通过“place”参数来告诉 Git 提交记录来自于 main, 要推送到远程仓库中的 main。它实际就是要同步的两个仓库的位置。</p>
<p>需要注意的是，因为我们通过指定参数告诉了 Git 所有它需要的信息, 所以它就忽略了我们所检出的分支的属性！</p>
<p>如</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push origin foo</span><br></pre></td></tr></table></figure>

<p>就是将本地的foo分支同步到远程origin 的foo分支</p>
<h3 id="5、-lt-place-gt-参数详解"><a href="#5、-lt-place-gt-参数详解" class="headerlink" title="5、&lt;place&gt;参数详解"></a>5、&lt;place&gt;参数详解</h3><p>当为 git push 指定 place 参数为 <code>main</code> 时，我们同时指定了提交记录的来源和去向。</p>
<p>你可能想问 —— 如果来源和去向分支的名称不同呢？比如你想把本地的 <code>foo</code> 分支推送到远程仓库中的 <code>bar</code> 分支。</p>
<p><strong>要同时为源和目的地指定 <code>&lt;place&gt;</code> 的话，只需要用冒号 <code>:</code> 将二者连起来就可以了：</strong></p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push origin &lt;source&gt;:&lt;destination&gt;</span><br></pre></td></tr></table></figure>

<p>这个参数实际的值是个 refspec，“refspec” 是一个自造的词，意思是 Git 能识别的位置（比如分支 <code>foo</code> 或者 <code>HEAD~1</code>）</p>
<p>一旦你指定了独立的来源和目的地，就可以组织出言简意赅的远程操作命令了，让我们看看演示！</p>
<p><strong>目前状态：</strong></p>
<img src="/2022/04/18/Learn-Git-Branching/git参数1-165028516951426.png" alt="git参数1" style="zoom:60%;">

<p>使用命令：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push origin foo^:main</span><br></pre></td></tr></table></figure>

<p><strong>目标状态：</strong></p>
<img src="/2022/04/18/Learn-Git-Branching/git参数2-165028516951425.png" alt="git参数2" style="zoom:60%;">

<p>记住，<code>source</code> 可以是任何 Git 能识别的位置。</p>
<p>这是个令人困惑的命令，但是它确实是可以运行的 —— Git 将 <code>foo^</code> 解析为一个位置，上传所有未被包含到远程仓库里 <code>main</code> 分支中的提交记录。</p>
<ul>
<li>如果你要推送到的目的分支不存在会怎么样呢？没问题！Git 会在远程仓库中根据你提供的名称帮你创建这个分支！</li>
</ul>
<h3 id="6、Git-fetch的参数"><a href="#6、Git-fetch的参数" class="headerlink" title="6、Git fetch的参数"></a>6、Git fetch的参数</h3><p><code>git fetch</code> 的参数和 <code>git push</code> 极其相似。他们的概念是相同的，只是方向相反罢了（因为现在你是下载，而非上传）</p>
<hr>
<p><strong><code>&lt;place&gt;</code> 参数</strong></p>
<p>如果你像如下命令这样为 git fetch 设置 的话：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git fetch origin foo</span><br></pre></td></tr></table></figure>

<p>Git 会到远程仓库的 <code>foo</code> 分支上，然后获取所有本地不存在的提交，放到本地的 <code>o/foo</code> 上。</p>
<p>注意：Git 会将新提交放到 <code>o/foo</code> 而不是放到我本地的 foo 分支。因为git fetch不会更新你的本地的非远程分支, 只是下载提交记录（这样, 你就可以对远程分支进行检查或者合并了）。因为你可能在 foo 分支上的工作还未完成，你也不想弄乱它。</p>
<hr>
<p>“如果我们指定 <code>&lt;source&gt;:&lt;destination&gt;</code> 会发生什么呢？”</p>
<p>如果你觉得直接更新本地分支很爽，那你就用冒号分隔的 refspec 吧。不过，你不能在当前检出的分支上干这个事，但是其它分支是可以的。</p>
<p>这里有一点是需要注意的 —— <code>source</code> 现在指的是远程仓库中的位置，而 <code>&lt;destination&gt;</code> 才是要放置提交的本地仓库的位置。它与 git push 刚好相反，这是可以讲的通的，因为我们在往相反的方向传送数据。</p>
<p>理论上虽然行的通，但开发人员很少这么做。这里介绍它主要是为了从概念上说明 <code>fetch</code> 和 <code>push</code> 的相似性，只是方向相反罢了。</p>
<p>另外，如果 <code>git fetch</code> 没有参数，它会下载所有的提交记录到各个远程分支。</p>
<h3 id="7、没有source的source"><a href="#7、没有source的source" class="headerlink" title="7、没有source的source"></a>7、没有source的source</h3><p>Git 有两种关于 <code>&lt;source&gt;</code> 的用法是比较诡异的，即你可以在 git push 或 git fetch 时不指定任何 <code>source</code>，方法就是仅保留冒号和 destination 部分，source 部分留空。</p>
<ul>
<li><code>git push origin :side</code></li>
<li><code>git fetch origin :bugFix</code></li>
</ul>
<p>如果 push 空 到远程仓库会如何呢？它会删除远程仓库中的分支！</p>
<p>如：</p>
<p>原状态：</p>
<img src="/2022/04/18/Learn-Git-Branching/nosource-165028516951427.png" alt="nosource" style="zoom:60%;">

<p>执行指令：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push origin:foo</span><br></pre></td></tr></table></figure>

<p>所得结果：</p>
<img src="/2022/04/18/Learn-Git-Branching/nosource2-165028516951428.png" alt="nosource2" style="zoom:60%;">

<p>就是这样子, 我们通过给 push 传空值 source，成功删除了远程仓库中的 <code>foo</code> 分支。</p>
<hr>
<p>如果 fetch 空 到本地，会在本地创建一个新分支。</p>
<p>如：</p>
<img src="/2022/04/18/Learn-Git-Branching/nosource3-165028516951429.png" alt="nosource3" style="zoom:60%;">

<p>执行命令：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git fetch origin:bar</span><br></pre></td></tr></table></figure>

<p>所得结果：</p>
<img src="/2022/04/18/Learn-Git-Branching/nosource4-165028516951430.png" alt="nosource4" style="zoom:60%;">

<h3 id="8、git-pull的参数"><a href="#8、git-pull的参数" class="headerlink" title="8、git pull的参数"></a>8、git pull的参数</h3><p><strong>git pull 到头来就是 fetch 后跟 merge 的缩写。你可以理解为用同样的参数执行 git fetch，然后再 merge 你所抓取到的提交记录。</strong></p>
<p>还可以和其它更复杂的参数一起使用, 来看一些例子:</p>
<p>以下命令在 Git 中是等效的:</p>
<p><code>git pull origin foo</code> 相当于：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git fetch origin foo; git merge o/foo</span><br></pre></td></tr></table></figure>

<p>还有…</p>
<p><code>git pull origin bar~1:bugFix</code> 相当于：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git fetch origin bar~1:bugFix; git merge bugFix</span><br></pre></td></tr></table></figure>

<p>看到了? git pull 实际上就是 fetch + merge 的缩写, git pull 唯一关注的是提交最终合并到哪里（也就是为 git fetch 所提供的 destination 参数）</p>
<p>如果我们指定要抓取的 place，所有的事情都会跟之前一样发生，只是增加了 merge 操作。</p>
<hr>
<p>pull 也可以用 source:destination </p>
<p>例如：</p>
<p>原状态：</p>
<img src="/2022/04/18/Learn-Git-Branching/pull的参数-165028516951431.png" alt="pull的参数" style="zoom:60%;">

<p>使用命令：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git pull origin main:foo</span><br></pre></td></tr></table></figure>

<p>所得结果：</p>
<img src="/2022/04/18/Learn-Git-Branching/pull参数2-165028516951432.png" alt="pull参数2" style="zoom:60%;">

<p>它先在本地创建了一个叫 <code>foo</code> 的分支，从远程仓库中的 main 分支中下载提交记录，并合并到 <code>foo</code>，然后再 merge 到我们的当前检出的分支 <code>bar</code> 上。</p>
</div></article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">Author: </span><span class="post-copyright-info"><a href="mailto:undefined">LiYang</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">Link: </span><span class="post-copyright-info"><a href="http://example.com/2022/04/18/Learn-Git-Branching/">http://example.com/2022/04/18/Learn-Git-Branching/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">Copyright Notice: </span><span class="post-copyright-info">All articles in this blog are licensed under <a target="_blank" rel="noopener" href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a> unless stating additionally.</span></div></div><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/%E6%8A%80%E6%9C%AF%E5%AD%A6%E4%B9%A0/">技术学习</a></div><nav id="pagination"><div class="prev-post pull-left"><a href="/2022/05/13/C-%E4%B8%AD%E7%9A%84%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/"><i class="fa fa-chevron-left">  </i><span>C++中的内存泄漏</span></a></div><div class="next-post pull-right"><a href="/2022/04/18/C-%E4%B8%AD%E7%9A%84operator-new%E5%92%8Cplacement-new/"><span>C++中的operator_new和placement_new</span><i class="fa fa-chevron-right"></i></a></div></nav></div></div><footer><div class="layout" id="footer"><div class="copyright">&copy;2013 - 2022 By LiYang</div><div class="framework-info"><span>Driven - </span><a target="_blank" rel="noopener" href="http://hexo.io"><span>Hexo</span></a><span class="footer-separator">|</span><span>Theme - </span><a target="_blank" rel="noopener" href="https://github.com/Molunerfinn/hexo-theme-melody"><span>Melody</span></a></div><div class="busuanzi"><script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><span id="busuanzi_container_page_pv"><i class="fa fa-file"></i><span id="busuanzi_value_page_pv"></span><span></span></span></div></div></footer><i class="fa fa-arrow-up" id="go-up" aria-hidden="true"></i><script src="https://cdn.jsdelivr.net/npm/animejs@latest/anime.min.js"></script><script src="https://cdn.jsdelivr.net/npm/jquery@latest/dist/jquery.min.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@latest/dist/jquery.fancybox.min.js"></script><script src="https://cdn.jsdelivr.net/npm/velocity-animate@latest/velocity.min.js"></script><script src="https://cdn.jsdelivr.net/npm/velocity-ui-pack@latest/velocity.ui.min.js"></script><script src="/js/utils.js?version=1.9.0"></script><script src="/js/fancybox.js?version=1.9.0"></script><script src="/js/sidebar.js?version=1.9.0"></script><script src="/js/copy.js?version=1.9.0"></script><script src="/js/fireworks.js?version=1.9.0"></script><script src="/js/transition.js?version=1.9.0"></script><script src="/js/scroll.js?version=1.9.0"></script><script src="/js/head.js?version=1.9.0"></script><script>if(/Android|webOS|iPhone|iPod|iPad|BlackBerry/i.test(navigator.userAgent)) {
  $('#nav').addClass('is-mobile')
  $('footer').addClass('is-mobile')
  $('#top-container').addClass('is-mobile')
}</script></body></html>